package com.xrui.hbase;


import com.xrui.hbase.filter.RowFilter;
import com.xrui.hbase.impl.HBaseScanOptions;

import java.io.Closeable;
import java.io.IOException;
import java.util.List;

/**
 * Interface for reading data from a HBase table.
 * <p>
 * <p>
 * Utilizes {@link EntityId} and {@link DataRequest}
 * to return {@link RowData} or a {@link RowScanner}
 * to iterate across rows in a HBase table.
 * </p>
 * <p>
 * <p>To create the three most recent versions of cell data from a column <code>bar</code> from
 * the family <code>foo</code> within the time range (123, 456):
 * <pre>{@code
 *   DataRequestBuilder builder = DataRequest.builder()
 *     .withTimeRange(123L, 456L);
 *     .newColumnsDef()
 *     .withMaxVersions(3)
 *     .add("foo", "bar");
 *   final DataRequest request = builder.build();
 *
 *   final TableReader reader = myTable.openTableReader();
 *   final RowData data = reader.create(myEntityId, request);
 * }</pre>
 * <p>
 * <p>To create a row scanner across many records using the same column and version restrictions
 * from above:
 * <pre>{@code
 *   final RowScanner scanner = reader.getScanner(request);
 *
 *   final ScannerOptions options = new ScannerOptions()
 *       .setStartRow(myStartRow)
 *       .setStopRow(myStopRow);
 *   final RowScanner limitedScanner = reader.getScanner(request, options);
 * }</pre>
 * <p>
 * If a KijiScannerOptions is not set, the scanner will iterate over all rows in the table
 * (as in the case of <code>scanner</code>).
 * <p>
 * By default, Kiji row scanners automatically handle HBase scanner timeouts and reopen the
 * HBase scanner as needed. This behavior may be disabled using
 * {@link ScannerOptions#setReopenScannerOnTimeout(boolean)}.
 * <p>
 * Finally, row caching may be configured via ScannerOptions.
 * By default, row caching is configured from the Hadoop Configuration property
 * {@code hbase.client.scanner.caching}.
 * </p>
 * <p>
 * <p> Instantiated in HBase Schema via {@link Table#openTableReader()}. </p>
 * <p>
 * Unless otherwise specified, readers are not thread-safe and must be synchronized externally.
 * </p>
 */
public interface TableReader extends Closeable {

    /**
     * Retrieves data from a single row in the table.
     *
     * @param entityId    The entity id for the row to create data from.
     * @param dataRequest Specifies the columns of data to retrieve.
     * @return The requested data. If there is no row for the specified entityId, this
     * will return an empty RowData. (containsColumn() will return false for all
     * columns.)
     * @throws IOException If there is an IO error.
     */
    RowData get(EntityId entityId, DataRequest dataRequest)
        throws IOException;

    /**
     * Retrieves data from a list of rows in the hbase table.
     *
     * @param entityIds   The list of entity ids to collect data for.
     * @param dataRequest Specifies constraints on the data to retrieve for each entity id.
     * @return The requested data.  If an EntityId specified in <code>entityIds</code>
     * does not exist, then the corresponding RowData will be empty.
     * If a create fails, then the corresponding RowData will be null (instead of empty).
     * @throws IOException If there is an IO error.
     */
    List<RowData> bulkGet(List<EntityId> entityIds, DataRequest dataRequest)
        throws IOException;

    /**
     * Gets a RowScanner with the specified data request.
     *
     * @param dataRequest The data request to scan for.
     * @return The RowScanner.
     * @throws IOException          If there is an IO error.
     * @throws DataRequestException If the data request is invalid.
     */
    RowScanner getScanner(DataRequest dataRequest)
        throws IOException;

    /**
     * Gets a RowScanner using the specified data request and options.
     *
     * @param dataRequest    The data request to scan for.
     * @param scannerOptions Other options for the scanner.
     * @return The RowScanner.
     * @throws IOException          If there is an IO error.
     * @throws DataRequestException If the data request is invalid.
     */
    RowScanner getScanner(DataRequest dataRequest, ScannerOptions scannerOptions)
        throws IOException;

    /**
     * Options for RowScanners.
     */
    public static final class ScannerOptions {
        /**
         * The start row for the scan.
         */
        private EntityId mStartRow = null;

        /**
         * The stop row for the scan.
         */
        private EntityId mStopRow = null;

        /**
         * The row filter for the scan.
         */
        private RowFilter mRowFilter = null;

        /**
         * When set, re-open scanners automatically on scanner timeout.
         */
        private boolean mReopenScannerOnTimeout = true;

        /**
         * Number of rows to fetch per RPC.
         * <ul>
         * <li> N&gt;1 means fetch N rows per RPC; </li>
         * <li> N=1 means no caching; </li>
         * <li> N&lt;1 means use the value from the configuration property
         * {@code hbase.client.scanner.caching}. </li>
         * </ul>
         */
        private int mRowCaching = -1;

        /**
         * The HBaseScanOptions to scan with for RowScanners
         * backed by an HBase scan.
         * <p>
         * Defaults to the default HBaseScanOptions if not set.
         */
        private HBaseScanOptions mHBaseScanOptions = new HBaseScanOptions();

        /**
         * Creates ScannerOptions with uninitialized options
         * and default HBaseScanOptions.
         */
        public ScannerOptions() {
        }

        /**
         * Gets the start row set in these options.
         *
         * @return The start row to use, null if unset.
         */
        public EntityId getStartRow() {
            return mStartRow;
        }

        /**
         * Sets the start row used by the scanner,
         * and returns this ScannerOptions to allow chaining.
         *
         * @param startRow The row to start scanning from.
         * @return This ScannerOptions with the start row set.
         */
        public ScannerOptions setStartRow(EntityId startRow) {
            mStartRow = startRow;
            return this;
        }

        /**
         * Gets the stop row set in these options.
         *
         * @return The stop row to use, null if unset.
         */
        public EntityId getStopRow() {
            return mStopRow;
        }

        /**
         * Sets the stop row used by the scanner,
         * and returns this ScannerOptions to allow chaining.
         *
         * @param stopRow The last row to scan.
         * @return This ScannerOptions with the stop row set.
         */
        public ScannerOptions setStopRow(EntityId stopRow) {
            mStopRow = stopRow;
            return this;
        }

        /**
         * Gets the row filter set in these options.
         *
         * @return The row filter to use, null if unset.
         */
        public RowFilter getRowFilter() {
            return mRowFilter;
        }

        /**
         * Sets the row filter used by the scanner,
         * and returns this ScannerOptions to allow chaining.
         *
         * @param rowFilter The row filter to use.
         * @return This ScannerOptions with the row filter set.
         */
        public ScannerOptions setRowFilter(RowFilter rowFilter) {
            mRowFilter = rowFilter;
            return this;
        }

        /**
         * Gets the HBaseScanOptions set in these options.
         *
         * @return The HBaseScanOptions to use; if unset, the default HbaseScanOptions.
         */
        public HBaseScanOptions getHBaseScanOptions() {
            return mHBaseScanOptions;
        }

        /**
         * Sets the HBaseScanOptions used by a HBase backed scanner.
         * The default is the default HBaseScanOptions.
         *
         * @param hBaseScanOptions The HBaseScanOptions to use.
         * @return This ScannerOptions with the HBaseScanOptions set.
         */
        public ScannerOptions setHBaseScanOptions(HBaseScanOptions hBaseScanOptions) {
            mHBaseScanOptions = hBaseScanOptions;
            return this;
        }

        /**
         * Reports whether the underlying HBase scanner should be reopened on timeout.
         *
         * @return whether the underlying HBase scanner should be reopened on timeout.
         */
        public boolean getReopenScannerOnTimeout() {
            return mReopenScannerOnTimeout;
        }

        /**
         * Configures whether the underlying HBase scanner should be automatically re-opened on timeout.
         * <p>
         * <p> By default, timeouts are handled and the HBase scanner is automatically reopened. </p>
         *
         * @param reopenScannerOnTimeout True means HBase scanner timeouts are automatically
         *                               handled to reopen new scanners. Otherwise ScannerTimeoutExceptions will be surfaced to
         *                               the user.
         * @return this ScannerOptions.
         */
        public ScannerOptions setReopenScannerOnTimeout(boolean reopenScannerOnTimeout) {
            mReopenScannerOnTimeout = reopenScannerOnTimeout;
            return this;
        }

        /**
         * Reports the number of rows to fetch per RPC to the region server.
         * <p>
         * <ul>
         * <li> N&gt;1 means fetch N rows per RPC; </li>
         * <li> N=1 means no caching; </li>
         * <li> N&lt;1 means use the value from the configuration property
         * {@code hbase.client.scanner.caching}. </li>
         * </ul>
         *
         * @return the number of rows to fetch per RPC to the region server.
         */
        public int getRowCaching() {
            return mRowCaching;
        }

        /**
         * Configures the row caching, ie. number of rows to fetch per RPC to the region server.
         * <p>
         * <ul>
         * <li> N&gt;1 means fetch N rows per RPC; </li>
         * <li> N=1 means no caching; </li>
         * <li> N&lt;1 means use the value from the configuration property
         * {@code hbase.client.scanner.caching}. </li>
         * </ul>
         * <p>
         * By default, this is the value of the configuration property
         * {@code hbase.client.scanner.caching}.
         * </p>
         *
         * @param rowCaching Number of rows to fetch per RPC to the region server.
         * @return this ScannerOptions.
         */
        public ScannerOptions setRowCaching(int rowCaching) {
            mRowCaching = rowCaching;
            return this;
        }

    }
}
